<template>
	<view class="componentBox">
		<view class="microphoneBox line-item-center">
			<view class="line-item-center flex-column" v-if="!running" @click="bindStart">
				<view>
					<view class="text">开始录音</view>
				</view>
			</view>
			<view class="line-item-center flex-column" v-else>
				<view class="resultBox" v-if="resultText">
					<scroll-view class="scroll-view" scroll-y>
						<view id="resultPage" class="resultPage">{{ resultText }}</view>
					</scroll-view>
				</view>
				<view class="line-item-center flex-column" @click="bindEnd">
					<view>停止录音</view>
					<view class="text">{{ Number(time / 1000).toFixed(1) }}s</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	/**
	 * @author: 1580043700@qq.com - Zhang Jun
	 * @date: 2022/04/13 10:05
	 * @description: 实时语音转写组件 - 科大讯飞版
	 */


	let CryptoJS = requirePlugin("CryptoJs");


	let recorderManager = uni.getRecorderManager();
	let wxst = null;
	let rtasrResult = [];
	let interval = null;

	export default {
		props: {},
		data() {
			return {
				running: false,
				time: 0,
				result: {},
				config: {
					// 请求地址
					hostUrl: "wss://rtasr.xfyun.cn/v1/ws",	
					//TODO 在控制台-我的应用-实时语音转写获取
					appid: "",
					apiKey: "",
				}
			};
		},
		computed: {
			resultText() {
				let result = this.result;
				return Object.values(result).join("");
			},
		},
		created() {},
		mounted() {
			recorderManager.onFrameRecorded(res => {
				wxst.send({
					data: res.frameBuffer,
					success: () => {
						console.warn("onFrameRecorded send ", res);
					},
				});
			});

			recorderManager.onStart(res => {
				this.running = true;
				this.time = 0;
				this.title = "";
				this.result = {};
				rtasrResult = [];
				console.warn("recorder onStart", res);
				this.bindInterval();
			});

			recorderManager.onStop(res => {
				uni.hideLoading();
				this.running = false;
				this.bindClearInterval();
				console.error("recorder onStop", res);
				wxst.send({
					data: '{"end": true}',
					success: () => {
						console.error("recorder onStop send ", res);
					},
				});
				if (this.resultText) {
					uni.showLoading({
						title: "转换中...",
						mask: true
					});
					this.PCM2Wav(
						res.tempFilePath,
						filePath => {
							uni.hideLoading();
							this.$emit("stop", {
								url: filePath,
								text: this.resultText
							});

							const fileSystemManager = uni.getFileSystemManager();
							fileSystemManager.removeSavedFile({
								filePath: res.tempFilePath
							});
							fileSystemManager.removeSavedFile({
								filePath: filePath
							});
						},
						() => {
							uni.showToast({
								title: "录音文件转码失败",
								icon: "none"
							});
						}
					);
				}
			});

			wx.onSocketOpen(res => {
				console.warn("onSocketOpen", res);
				recorderManager.start({
					duration: 600000, //指定录音的时长，单位 ms
					sampleRate: 16000, //采样率
					numberOfChannels: 1,
					format: "PCM", //音频格式
					frameSize: 2, //指定帧大小，单位 KB
				});
			});

			uni.onSocketMessage(res => {
				let result = JSON.parse(res.data);
				console.warn("onSocketMessage", result);
				if (result.data && result.code == "0") {
					let data = JSON.parse(result.data);
					rtasrResult[data.seg_id] = data;
					if (data.cn.st.type == 0) {
						rtasrResult.forEach(i => {
							let str = "";
							if (i.cn.st.type == 0) {
								i.cn.st.rt.forEach(j => {
									j.ws.forEach(k => {
										k.cw.forEach(l => {
											str += l.w;
										});
									});
								});
							}

							this.result = {
								...this.result,
								[data.seg_id]: str
							};
						});
					}
					return;
				}

				uni.hideLoading();

				if (result.code == "10105") {
					uni.showToast({
						title: "语音识别,没有权限",
						icon: "none"
					});
				} else if (result.code == "10106") {
					uni.showToast({
						title: "语音识别,无效参数",
						icon: "none"
					});
				} else if (result.code == "10107") {
					uni.showToast({
						title: "语音识别,非法参数值",
						icon: "none"
					});
				} else if (result.code == "10110") {
					uni.showToast({
						title: "语音识别,无授权许可",
						icon: "none"
					});
				} else if (result.code == "10700") {
					uni.showToast({
						title: "语音识别,引擎错误",
						icon: "none"
					});
				} else if (result.code == "10202") {
					uni.showToast({
						title: "语音识别,连接错误",
						icon: "none"
					});
				} else if (result.code == "10204") {
					uni.showToast({
						title: "语音识别,写错误",
						icon: "none"
					});
				} else if (result.code == "10205") {
					uni.showToast({
						title: "语音识别,读错误",
						icon: "none"
					});
				} else if (result.code == "16003") {
					uni.showToast({
						title: "语音识别,基础组件异常",
						icon: "none"
					});
				} else if (result.code == "10800") {
					uni.showToast({
						title: "语音识别,超过授权的连接数",
						icon: "none"
					});
				}
				if (this.running) {
					this.running = false;
					this.bindClearInterval();
				}
			});
			uni.onSocketClose(res => {
				uni.hideLoading();
				console.error("onSocketClose", res);
				uni.closeSocket();
				recorderManager?.stop();
				this.running = false;
				this.bindClearInterval();
			});
			uni.onSocketError(res => {
				console.error("onSocketError", res);
			});
		},
		methods: {
			bindStart() {
				let url = this.getURL();
				wxst = uni.connectSocket({
					url: url,
					method: "GET",
					success: () => {
						console.warn("connectSocket");
					},
					fail: error => {
						uni.showToast({
							title: "创建Socket失败",
							icon: "none"
						});
						console.error("connectSocket", error);
					},
				});
			},
			bindEnd() {
				if (this.time < 3000) {
					uni.showToast({
						title: "说话时间太短了",
						icon: "none"
					});
				} else {
					uni.showLoading({
						title: "识别中...",
					});
					recorderManager.stop();
				}
			},
			uploadRecorder(url, okCall) {
				okCall(data);
				// uploadFile(url)
				// 	.then(data => {
				// 		okCall(data);
				// 	})
				// 	.catch(() => {
				// 		uni.hideLoading();
				// 		uni.showModal({
				// 			title: "提示",
				// 			content: "保存语音失败",
				// 			showCancel: false,
				// 			success: () => {
				// 				this.uploadRecorder(url, okCall);
				// 			},
				// 		});
				// 	});
			},
			bindInterval() {
				interval = setInterval(() => {
					this.time = Number(this.time) + 100;
				}, 100);
			},
			bindClearInterval() {
				interval && clearInterval(interval);
				interval = null;
			},
			// 一些工具函数，将数据进行转码、封装
			encodeWAV(samples, numChannels, sampleRate) {
				var buffer = new ArrayBuffer(44 + samples.byteLength);
				var view = new DataView(buffer);
				/* RIFF identifier */
				this.writeString(view, 0, "RIFF");
				/* RIFF chunk length */
				view.setUint32(4, 36 + samples.byteLength, true);
				/* RIFF type */
				this.writeString(view, 8, "WAVE");
				/* format chunk identifier */
				this.writeString(view, 12, "fmt ");
				/* format chunk length */
				view.setUint32(16, 16, true);
				/* sample format (raw) */
				view.setUint16(20, 1, true);
				/* channel count */
				view.setUint16(22, numChannels, true);
				/* sample rate */
				view.setUint32(24, sampleRate, true);
				/* byte rate (sample rate * block align) */
				view.setUint32(28, sampleRate * 4, true);
				/* block align (channel count * bytes per sample) */
				view.setUint16(32, numChannels * 2, true);
				/* bits per sample */
				view.setUint16(34, 16, true);
				/* data chunk identifier */
				this.writeString(view, 36, "data");
				/* data chunk length */
				view.setUint32(40, samples.byteLength, true);

				this.copyBytes(view, 44, samples);

				return view;
			},
			copyBytes(output, offset, input) {
				const dataView = new DataView(input);
				for (var i = 0; i < input.byteLength; i++, offset++) {
					output.setInt8(offset, dataView.getInt8(i));
				}
			},
			writeString(view, offset, string) {
				for (var i = 0; i < string.length; i++) {
					view.setUint8(offset + i, string.charCodeAt(i));
				}
			},
			PCM2Wav(filepath, okCal, errorCal) {
				const fileSystemManager = uni.getFileSystemManager();
				fileSystemManager.readFile({
					filePath: filepath,
					success: res => {
						let view = this.encodeWAV(res.data, 1, 16000);
						let dateNow = Date.now();
						fileSystemManager.writeFile({
							data: view.buffer,
							filePath: `${wx.env.USER_DATA_PATH}/${dateNow}.wav`,
							success: () => {
								okCal(`${wx.env.USER_DATA_PATH}/${dateNow}.wav`);
							},
							fail: () => {
								errorCal();
							},
						});
					},
				});
			},
			getURL() {
				const config = this.config
				let ts = parseInt(new Date().getTime() / 1000)
				return config.hostUrl + "?appid=" + config.appid + "&ts=" + ts + "&signa=" + this.getSigna(ts);
			},
			// 鉴权签名
			getSigna(ts) {
				const config = this.config
				let md5 = CryptoJS.MD5(config.appid + ts).toString()
				let sha1 = CryptoJS.HmacSHA1(md5, config.apiKey)
				let base64 = CryptoJS.enc.Base64.stringify(sha1)
				return encodeURIComponent(base64)
			},
		},
		destroyed() {
			uni.hideLoading();
			uni.closeSocket();
			recorderManager?.stop();
			this.bindClearInterval();
		},
	};
</script>

<style scoped lang="scss">
	.componentBox {
		.microphoneBox {
			.resultBox {
				background-color: rgba(0, 0, 0, 0.3);
				border-radius: 20rpx;

				.scroll-view {
					font-size: 32rpx;
					padding: 20rpx;
					max-height: 300rpx;
					color: white;
				}
			}

			.text {
				font-size: 32rpx;
				text-align: center;
				padding: 10rpx 20rpx;
				min-width: 60rpx;
				border-radius: 40rpx;
				background-color: rgba(0, 0, 0, 0.2);
			}
		}
	}
</style>
